package tests;

import static org.junit.Assert.*;

import java.awt.geom.Point2D;
import java.util.ArrayList;
import java.util.Properties;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;

import ownserver.ServerShip;
import ownserver.ServerVisibility;
import ownserver.ServerTurret;

import base.Arc;
import base.ShipSilhouette;

import com.sun.sgs.app.AppContext;
import com.sun.sgs.app.DataManager;
import com.sun.sgs.app.ManagedReference;
import com.sun.sgs.app.util.ScalableList;
import com.sun.sgs.auth.Identity;
import com.sun.sgs.kernel.TransactionScheduler;
import com.sun.sgs.test.util.SgsTestNode;
import com.sun.sgs.test.util.TestAbstractKernelRunnable;

public class ServerVisibilityTest {

	/** The server node */
	private static SgsTestNode serverNode;
	
    private static TransactionScheduler txnScheduler;
    private static Identity taskOwner;
    //private static DataService dataService;
    
    private static final Logger logger =
            Logger.getLogger(ServerVisibilityTest.class.getName());
    
    private static double errorLimit = 1E-12;
	
	/** The test case name */
	private static final String NAME = "ShipGeometryTest";
	
	@BeforeClass
	public static void setUpBeforeClass() throws Exception {
		Properties p = SgsTestNode.getDefaultProperties(NAME, null, SgsTestNode.DummyAppListener.class);
        
		p.setProperty("com.sun.sgs.txn.timeout", "4294967"); // for debugging
		
        serverNode = new SgsTestNode(NAME, null, p);
        
        
        txnScheduler = serverNode.getSystemRegistry().
                getComponent(TransactionScheduler.class);
        taskOwner = serverNode.getProxy().getCurrentOwner();
        //dataService = serverNode.getDataService();
	}

	@AfterClass
	public static void tearDownAfterClass() throws Exception {
		serverNode.shutdown(true);
	}
	
	@Test
	public void testGenerateShipSilhouette() throws Exception {
		txnScheduler.runTask(
			new TestAbstractKernelRunnable() {
			public void run() throws Exception {
				ShipSilhouette silh;
				Point2D.Double turret = new Point2D.Double(0,0);
				Arc towards = new Arc(30, 150);
				double rangeLimit = 40;
				// null test
				silh = ServerVisibility.generateShipSilhouette(null, turret, towards, rangeLimit * rangeLimit);
				assertTrue(silh == null);
				
				// a ship with heading 0, at x=0, y = 20
				// length 20 and width 4
				ServerShip s = getTestShip(0, 20, 0, 0, 0, 0);
				
				silh = ServerVisibility.generateShipSilhouette(s, turret, towards, rangeLimit * rangeLimit);
				assertTrue(silh != null);
				closeEnough(silh.getBeginCorner().x, 10);
				closeEnough(silh.getEndCorner().x, -10);
				closeEnough(silh.getBeginCorner().y, 20-2);
				closeEnough(silh.getEndCorner().y, 20-2);
				
				double angle = Math.toDegrees(Math.atan(10.0/18.0));
				closeEnough(silh.fromTurret.beginAngle, 90-angle);
				closeEnough(silh.fromTurret.endAngle, 90+angle);
				
				towards = new Arc(300, 180);
				silh = ServerVisibility.generateShipSilhouette(s, turret, towards, rangeLimit * rangeLimit);
				assertTrue(silh != null);
				
				// target ship is not within the range limit
				silh = ServerVisibility.generateShipSilhouette(s, turret, towards, rangeLimit);
				assertTrue(silh == null);
				
				// target ship is not in the arc
				Arc somewhereelse = new Arc(260, 280);
				silh = ServerVisibility.generateShipSilhouette(s, turret, somewhereelse, rangeLimit * rangeLimit);
				assertTrue(silh == null);
				
				// target ship is in the arc, and goes over the 360 line. beginAngle and endAngle must be caught right!
				s = getTestShip(20, 0, 90, 0, 0, 0);
				towards = new Arc(280, 80);
				silh = ServerVisibility.generateShipSilhouette(s, turret, towards, rangeLimit * rangeLimit);
				assertTrue(silh != null);
				closeEnough(silh.getBeginCorner().x, 20-2);
				closeEnough(silh.getBeginCorner().y, -10);
				closeEnough(silh.getEndCorner().x, 20-2);
				closeEnough(silh.getEndCorner().y, 10);
				
				angle = Math.toDegrees(Math.atan(10.0/18.0));
				closeEnough(silh.fromTurret.beginAngle, 360-angle);
				closeEnough(silh.fromTurret.endAngle, angle);
				
				// target ship fills the whole arc 
				s = getTestShip(0, 12, 0, 0, 0, 0);
				towards = new Arc(60, 120);
				silh = ServerVisibility.generateShipSilhouette(s, turret, towards, rangeLimit * rangeLimit);
				assertTrue(silh != null);
				closeEnough(silh.fromTurret.beginAngle, 60);
				closeEnough(silh.fromTurret.endAngle, 120);
				closeEnough(silh.getBeginCorner().y, 10);
				closeEnough(silh.getEndCorner().y, 10);

				// target ship fills half the arc
				ServerShip partial = getTestShip(10, 12, 0, 0, 45, 100);
				silh = ServerVisibility.generateShipSilhouette(partial, turret, towards, rangeLimit * rangeLimit);
				assertTrue(silh != null);
				closeEnough(silh.fromTurret.beginAngle, 60);
				closeEnough(silh.fromTurret.endAngle, 90);
				
				ServerShip heading = getTestShip(10, 12, 45, 0, 45, 100);
				silh = ServerVisibility.generateShipSilhouette(heading, turret, towards, rangeLimit * rangeLimit);
				assertTrue(silh != null);

			}
			}, taskOwner);
	}

	@Test
	public void testGetFiringAngle() throws Exception {
		txnScheduler.runTask(
				new TestAbstractKernelRunnable() {
				public void run() throws Exception {
					ScalableList<ServerShip> ships = new ScalableList<ServerShip> ();
					DataManager dataManager = AppContext.getDataManager();
					ManagedReference<ScalableList<ServerShip>> shipsRef = dataManager.createReference(ships);
					
					ServerVisibility geom = new ServerVisibility(shipsRef);
					
					double angle;
					
					// this ship's heading is 0 i.e. straight right, and the turret arc opens to our left, i.e. straight up
					ServerShip shooter = getTestShip(-7, -1, 0, 10, 170, 100);
					ServerShip targetInArc = getTestShip(0, 12, 0, 0, 180, 100);
					
					// getTestShip: double x, double y, double heading, double beginAngle, double endAngle, double optimal
					ships.add(shooter);
					ships.add(targetInArc);
					ServerTurret turret = shooter.getTurret(0);
					
					// target is in the arc partially, not blocked
					angle = geom.getFiringAngle(targetInArc, shooter, turret);
					closeEnough(angle, (45.0 + 90.0 + 45.0)/2);
					
					// target not in arc
					ServerShip targetNotInArc = getTestShip(0, -60, 0,0,0,0);
					ships.add(targetNotInArc);
					angle = geom.getFiringAngle(targetNotInArc, shooter, turret);
					assertTrue(Double.isNaN(angle));
					
					// target in arc, but not within optimal
					ships.remove(shooter);
					shooter = getTestShip(-7, -1, 0, 60, 120, 1);
					ships.add(shooter);
					turret = shooter.getTurret(0);
					angle = geom.getFiringAngle(targetNotInArc, shooter, turret);
					assertTrue(Double.isNaN(angle));
					
					// target fills the arc, not blocked
					/*
					 * TTTTT
					 * 
					 * 
					 * 
					 *   O
					 */
					ships.remove(shooter);
					shooter = getTestShip(-7, -1, 0, 60, 120, 100);
					ships.add(shooter);
					turret = shooter.getTurret(0);
					angle = geom.getFiringAngle(targetInArc, shooter, turret);
					closeEnough(angle, (60+120)/2);
					
					// target fills the arc and is fully blocked by one ship
					/*
					 * TTTTT
					 * 
					 * BBBBB
					 * 
					 *   O
					 */
					ServerShip blocker1 = getTestShip(0, 8, 0, 0, 180, 100);
					ships.add(blocker1);
					angle = geom.getFiringAngle(targetInArc, shooter, turret);
					assertTrue(Double.isNaN(angle));
					
					// target is in the arc partially and is fully blocked by one ship
					/*
					 * TTTTT
					 * 
					 * BBBBB
					 * 
					 *   O
					 */
					ships.remove(shooter);
					shooter = getTestShip(-7, -1, 0, 10, 170, 100);
					ships.add(shooter);
					turret = shooter.getTurret(0);
					angle = geom.getFiringAngle(targetInArc, shooter, turret);
					assertTrue(Double.isNaN(angle));
										
					// target is in the arc partially and is partially blocked from a side by one ship
					/*
					 * TTTTTT
					 * 
					 *    BBBBB
					 * 
					 *   OO
					 */
					blocker1.setX(10);
					angle = geom.getFiringAngle(targetInArc, shooter, turret);
					closeEnough(angle, (90.0+90.0+45.0)/2);
					
					// target is in the arc partially and is partially blocked from the middle of the arc by one ship
					/*
					 * TTTTT
					 * 
					 *   B
					 *   B
					 *   B
					 *   B
					 *   B
					 *   
					 *   O
					 */
					targetInArc.setY(32);
					blocker1.setX(0);
					blocker1.setY(20);
					blocker1.setHeading(270);
					angle = geom.getFiringAngle(targetInArc, shooter, turret);
					double angle2 = Math.toDegrees(Math.atan(3/1));
					double angle3 = Math.toDegrees(Math.atan(2.0/10.0));
					closeEnough(angle, (angle2 + 90.0 - angle3)/2);
					
					// target is in the arc partially and is partially blocked by two other ships
					/*    TTTTT
					 * 
					 * BBBBB BBBBB
					 *      O
					 */
					targetInArc.setY(16);
					targetInArc.setX(0);
					blocker1.setY(4);
					blocker1.setX(12);
					blocker1.setHeading(0);
					ServerShip blocker2 = getTestShip(-12, 4, 0, 0, 0, 0);
					ships.add(blocker2);
					angle = geom.getFiringAngle(targetInArc, shooter, turret);
					angle2 = Math.toDegrees(Math.atan(2.0/6.0));
					closeEnough(angle, (90.0 - angle + 90 + angle)/2);
					
					
					// target is in the arc partially and is fully blocked by three other ships
					/*    TTTTT
					 *       
					 *     BBBBB 
					 * BBBBB BBBBB
					 */
					ServerShip blocker3 = getTestShip(6, 10, 0, 0, 0, 0);
					ships.add(blocker3);
					angle = geom.getFiringAngle(targetInArc, shooter, turret);
					assertTrue(Double.isNaN(angle));
					
					// target is in the arc partially, goes over the 360 line, and is partially blocked by other ships
					/*
					 *    B
					 *    B   T
					 *    B   T
					 * O      T
					 *    B   T
					 *    B   T
					 *    B
					 */
					ships.remove(shooter);
					shooter = getTestShip(-7, -1, 0, 280, 80, 100);
					ships.add(shooter);
					turret = shooter.getTurret(0);
					
					ships.remove(blocker3);
					targetInArc.setY(0);
					targetInArc.setX(16);
					targetInArc.setHeading(90);
					
					blocker2.setHeading(90);
					blocker1.setHeading(90);
					blocker1.setX(4);
					blocker1.setY(12);
					blocker2.setX(4);
					blocker2.setY(-12);
					angle = geom.getFiringAngle(targetInArc, shooter, turret);
					closeEnough(angle, 0.0);
				}
				}, taskOwner);
	}

	@Test
	public void testCombineContinousArcs() {
		Arc separate1 = new Arc(10, 20);
		
		Arc overlap1 = new Arc(30, 40);
		
		Arc overlap2 = new Arc(35, 45);
		
		Arc separate2 = new Arc(150, 160);
		
		Arc overlap4 = new Arc(350, 35);
		
		Arc full = new Arc(0,0);
		
		ArrayList<Arc> result;
		
		ArrayList<Arc> a = new ArrayList<Arc> ();
		
		// empty list, should get empty list
		result = ServerVisibility.combineContinousArcs(a);
		assertTrue(result.isEmpty() == true);
		
		// single arc, nothing should change
		a.add(separate1);
		result = ServerVisibility.combineContinousArcs(a);
		assertTrue(result.size() == 1);
		assertTrue(result.get(0).equals(separate1));

		// remember, function assumes the given list is sorted
		
		// two arcs not overlapping
		a.add(overlap1);
		result = ServerVisibility.combineContinousArcs(a);
		assertTrue(result.size() == 2);
		assertTrue(result.get(0).equals(separate1));
		assertTrue(result.get(1).equals(overlap1));
		
		// one separate, two overlapping
		a.add(overlap2);
		result = ServerVisibility.combineContinousArcs(a);
		assertTrue(result.size() == 2);
		assertTrue(result.get(0).equals(separate1));
		assertTrue(result.get(1).beginAngle == overlap1.beginAngle &&
				result.get(1).endAngle == overlap2.endAngle);
		
		// two arcs that overlap
		a.remove(0);
		result = ServerVisibility.combineContinousArcs(a);
		assertTrue(result.size() == 1);
		assertTrue(result.get(0).beginAngle == overlap1.beginAngle &&
				result.get(0).endAngle == overlap2.endAngle);
		
		// two overlapping, one that does not
		a.add(separate2);
		result = ServerVisibility.combineContinousArcs(a);
		assertTrue(result.size() == 2);
		assertTrue(result.get(0).beginAngle == overlap1.beginAngle &&
				result.get(0).endAngle == overlap2.endAngle);
		assertTrue(result.get(1).equals(separate2));
		
		// overlapping with self
		a = new ArrayList<Arc> ();
		a.add(separate1);
		a.add(separate1);
		result = ServerVisibility.combineContinousArcs(a);
		assertTrue(result.size() == 1);
		assertTrue(result.get(0).equals(separate1));
		
		// overlapping with full circle
		a = new ArrayList<Arc> ();
		a.add(full);
		a.add(overlap1);
		result = ServerVisibility.combineContinousArcs(a);
		assertTrue(result.size() == 1);
		assertTrue(result.get(0).equals(full));
		
		// overlapping with 350 -> 10, i.e. crossing the 360 line
		a = new ArrayList<Arc> ();
		a.add(overlap4);
		a.add(overlap1);
		result = ServerVisibility.combineContinousArcs(a);
		assertTrue(result.size() == 1);
		assertTrue(result.get(0).beginAngle == overlap4.beginAngle &&
				result.get(0).endAngle == overlap1.endAngle);
		
		// and the result should be the same if two arcs are in different order
		a = new ArrayList<Arc> ();
		a.add(overlap1);
		a.add(overlap4);
		result = ServerVisibility.combineContinousArcs(a);
		assertTrue(result.size() == 1);
		assertTrue(result.get(0).beginAngle == overlap4.beginAngle &&
				result.get(0).endAngle == overlap1.endAngle);
		
	}

	@Test
	public void testGetAnglesToTarget() throws Exception {
		txnScheduler.runTask(
				new TestAbstractKernelRunnable() {
				public void run() throws Exception {
					ScalableList<ServerShip> ships = new ScalableList<ServerShip> ();
					DataManager dataManager = AppContext.getDataManager();
					ManagedReference<ScalableList<ServerShip>> shipsRef = dataManager.createReference(ships);
					
					ServerVisibility geom = new ServerVisibility(shipsRef);
					
					
					ArrayList<Arc> angles;
					
					// this ship's heading is 0 i.e. straight right, and the turret arc opens to our left, i.e. straight up
					ServerShip shooter = getTestShip(-7, -1, 0, 10, 170, 100);
					ServerShip targetInArc = getTestShip(0, 12, 0, 0, 180, 100);
					
					// getTestShip: double x, double y, double heading, double beginAngle, double endAngle, double optimal
					ships.add(shooter);
					ships.add(targetInArc);
					ServerTurret turret = shooter.getTurret(0);
					Point2D.Double turretPos = turret.getPos();
					ShipSilhouette tgt = ServerVisibility.generateShipSilhouette(targetInArc, turretPos, turret.getArc(), turret.getOptimal() * turret.getOptimal());
					double minDistSq = tgt.getMinDistanceSq();
					assertTrue(tgt != null);
					
					// target is in the arc partially, not blocked
					angles = geom.getAnglesToTarget(tgt, turretPos, shooter, minDistSq);
					closeEnough(angles.size(), 1);
					closeEnough(angles.get(0).beginAngle, 45);
					closeEnough(angles.get(0).endAngle, 90+45);
					
					// target fills the arc, not blocked
					/*
					 * TTTTT
					 * 
					 * 
					 * 
					 *   O
					 */
					ships.remove(shooter);
					shooter = getTestShip(-7, -1, 0, 60, 120, 100);
					ships.add(shooter);
					turret = shooter.getTurret(0);
					tgt = ServerVisibility.generateShipSilhouette(targetInArc, turretPos, turret.getArc(), turret.getOptimal() * turret.getOptimal());
					angles = geom.getAnglesToTarget(tgt, turretPos, shooter, minDistSq);
					closeEnough(angles.size(), 1);
					closeEnough(angles.get(0).beginAngle, 60);
					closeEnough(angles.get(0).endAngle, 120);
					
					// target fills the arc and is fully blocked by one ship
					/*
					 * TTTTT
					 * 
					 * BBBBB
					 * 
					 *   O
					 */
					ServerShip blocker1 = getTestShip(0, 8, 0, 0, 180, 100);
					ships.add(blocker1);
					tgt = ServerVisibility.generateShipSilhouette(targetInArc, turretPos, turret.getArc(), turret.getOptimal() * turret.getOptimal());
					angles = geom.getAnglesToTarget(tgt, turretPos, shooter, minDistSq);
					closeEnough(angles.size(), 0);
					
					// target is in the arc partially and is fully blocked by one ship
					/*
					 * TTTTT
					 * 
					 * BBBBB
					 * 
					 *   O
					 */
					ships.remove(shooter);
					shooter = getTestShip(-7, -1, 0, 10, 170, 100);
					ships.add(shooter);
					turret = shooter.getTurret(0);
					tgt = ServerVisibility.generateShipSilhouette(targetInArc, turretPos, turret.getArc(), turret.getOptimal() * turret.getOptimal());
					angles = geom.getAnglesToTarget(tgt, turretPos, shooter, minDistSq);
					closeEnough(angles.size(), 0);
										
					// target is in the arc partially and is partially blocked from a side by one ship
					/*
					 * TTTTTT
					 * 
					 *    BBBBB
					 * 
					 *   OO
					 */
					blocker1.setX(10);
					angles = geom.getAnglesToTarget(tgt, turretPos, shooter, minDistSq);
					closeEnough(angles.size(), 1);
					closeEnough(angles.get(0).beginAngle, 90);
					closeEnough(angles.get(0).endAngle, 90+45);
					
					// target is in the arc partially and is partially blocked from the middle of the arc by one ship
					/*
					 * TTTTT
					 * 
					 *   B
					 *   B
					 *   B
					 *   B
					 *   B
					 *   
					 *   O
					 */
					targetInArc.setY(32);
					tgt = ServerVisibility.generateShipSilhouette(targetInArc, turretPos, turret.getArc(), turret.getOptimal() * turret.getOptimal());
					blocker1.setX(0);
					blocker1.setY(20);
					blocker1.setHeading(270);
					angles = geom.getAnglesToTarget(tgt, turretPos, shooter, minDistSq);
					double angle = Math.toDegrees(Math.atan(3/1));
					double angle2 = Math.toDegrees(Math.atan(2.0/10.0));
					closeEnough(angles.size(), 2);
					closeEnough(angles.get(0).beginAngle, angle);
					closeEnough(angles.get(0).endAngle, 90-angle2);
					closeEnough(angles.get(1).beginAngle, 90+angle2);
					closeEnough(angles.get(1).endAngle, 180 - angle);
					
					// target is in the arc partially and is partially blocked by two other ships
					/*    TTTTT
					 * 
					 * BBBBB BBBBB
					 *      O
					 */
					targetInArc.setY(16);
					targetInArc.setX(0);
					tgt = ServerVisibility.generateShipSilhouette(targetInArc, turretPos, turret.getArc(), turret.getOptimal() * turret.getOptimal());
					blocker1.setY(4);
					blocker1.setX(12);
					blocker1.setHeading(0);
					
					ServerShip blocker2 = getTestShip(-12, 4, 0, 0, 0, 0);
					ships.add(blocker2);
					
					angles = geom.getAnglesToTarget(tgt, turretPos, shooter, minDistSq);
					angle = Math.toDegrees(Math.atan(2.0/6.0));
					closeEnough(angles.size(), 1);
					closeEnough(angles.get(0).beginAngle, 90-angle);
					closeEnough(angles.get(0).endAngle, 90+angle);
					
					// target is in the arc partially and is fully blocked by three other ships
					/*    TTTTT
					 *       
					 *     BBBBB 
					 * BBBBB BBBBB
					 */
					ServerShip blocker3 = getTestShip(6, 10, 0, 0, 0, 0);
					ships.add(blocker3);
					angles = geom.getAnglesToTarget(tgt, turretPos, shooter, minDistSq);
					closeEnough(angles.size(), 0);
					
					// target is in the arc partially, goes over the 360 line, and is partially blocked by other ships
					/*
					 *    B
					 *    B   T
					 *    B   T
					 * O      T
					 *    B   T
					 *    B   T
					 *    B
					 */
					ships.remove(shooter);
					shooter = getTestShip(-7, -1, 0, 280, 80, 100);
					ships.add(shooter);
					turret = shooter.getTurret(0);
					
					ships.remove(blocker3);
					targetInArc.setY(0);
					targetInArc.setX(16);
					targetInArc.setHeading(90);
					tgt = ServerVisibility.generateShipSilhouette(targetInArc, turretPos, turret.getArc(), turret.getOptimal() * turret.getOptimal());
					blocker2.setHeading(90);
					blocker1.setHeading(90);
					blocker1.setX(4);
					blocker1.setY(12);
					blocker2.setX(4);
					blocker2.setY(-12);
					angles = geom.getAnglesToTarget(tgt, turretPos, shooter, minDistSq);
					closeEnough(angles.size(), 1);
					closeEnough(angles.get(0).beginAngle, 360-angle);
					closeEnough(angles.get(0).endAngle, angle);

				}
				}, taskOwner);
	}

	
	@Test
	public void testGetBlockingSilhuettes() throws Exception {
		txnScheduler.runTask(
		new TestAbstractKernelRunnable() {
		public void run() throws Exception {
			ScalableList<ServerShip> ships = new ScalableList<ServerShip> ();
			DataManager dataManager = AppContext.getDataManager();
			ManagedReference<ScalableList<ServerShip>> shipsRef = dataManager.createReference(ships);
			
			ServerVisibility geom = new ServerVisibility(shipsRef);
			
			// null target, should get empty list
			double minDistSq = 100 * 100;
			ArrayList<ShipSilhouette> silhs = geom.getBlockingSilhuettes(null, new Point2D.Double(0,0), null, minDistSq);
			assertTrue(silhs.isEmpty() == true);
			
			// only test cases where the generateShipSilhouette of the target returns non-null, 
			// i.e. our target is within the arc and range limit of the turret.
			// other cases are tested in testGenerateShipSilhouette
			
			// only shooter and target in list, should get empty list
			// this ship's heading is 0 i.e. straight right, and the turret arc opens to our left, i.e. straight up
			ServerShip shooter = getTestShip(-7, -1, 0, 10, 170, 100);
			ServerShip targetInArc = getTestShip(0, 60, 0, 0, 180, 100);
			
			// getTestShip: double x, double y, double heading, double beginAngle, double endAngle, double optimal
			ships.add(shooter);
			ships.add(targetInArc);
			ServerTurret turret = shooter.getTurret(0);
			ShipSilhouette tgt = ServerVisibility.generateShipSilhouette(targetInArc, turret.getPos(), turret.getArc(), minDistSq);
			assertTrue(tgt != null);
			silhs = geom.getBlockingSilhuettes(tgt, turret.getPos(), shooter, minDistSq);
			assertTrue(silhs.isEmpty() == true);
			
			// one additional ship in list, not blocking
			ServerShip s2 = getTestShip(0, -60, 0, 0, 0, 0);
			ships.add(s2);
			silhs = geom.getBlockingSilhuettes(tgt, turret.getPos(), shooter, minDistSq);
			assertTrue(silhs.isEmpty() == true);
			
			// target, one blocker and one non-blocker in list, should get one entry
			ServerShip s3 = getTestShip(0, 12, 0, 0, 45, 100);
			ships.add(s3);
			silhs = geom.getBlockingSilhuettes(tgt, turret.getPos(), shooter, minDistSq);
			closeEnough(silhs.size(), 1);
			assertTrue(silhs.get(0).getShip() == s3);
			closeEnough(silhs.get(0).fromTurret.beginAngle, tgt.fromTurret.beginAngle);
			closeEnough(silhs.get(0).fromTurret.endAngle, tgt.fromTurret.endAngle);
			
			// target, one partial blocker in list, should get one entry
			s3.setX(10);
			silhs = geom.getBlockingSilhuettes(tgt, turret.getPos(), shooter, minDistSq);
			closeEnough(silhs.size(), 1);
			assertTrue(silhs.get(0).getShip() == s3);
			closeEnough(silhs.get(0).fromTurret.beginAngle, tgt.fromTurret.beginAngle);
			closeEnough(silhs.get(0).fromTurret.endAngle, 90);
			
			// target, one rotated blocker in list
			s3.setHeading(45);
			s3.setX(0);
			silhs = geom.getBlockingSilhuettes(tgt, turret.getPos(), shooter, minDistSq);
			closeEnough(silhs.size(), 1);
			
			s3.setX(0);
			s3.setY(18);
			s3.setHeading(270);
			silhs = geom.getBlockingSilhuettes(tgt, turret.getPos(), shooter, minDistSq);
			closeEnough(silhs.size(), 1);
			
			// target and two partial blockers in list
		}
		}, taskOwner);
	}
	
	public static ServerShip getTestShip(double x, double y, double heading, double beginAngle, double endAngle, double optimal)
	{
		ServerShip s = new ServerShip(0,0,x,y,0,0);
		s.setHeading(heading);
		double tx = 7;
		double ty = 1;
		// attach turret
		DataManager dataManager = AppContext.getDataManager();
		
		ManagedReference<ServerShip> shipref = dataManager.createReference(s);
		
		ServerTurret turret = new ServerTurret(shipref, beginAngle, endAngle, tx, ty, optimal, 0, 0, 0, 0, 0);
		s.attachTurret(turret);
		return s;
	}

	public void closeEnough(double a, double b)
	{
		if (Math.abs(a-b) < errorLimit == false)
		{
			logger.log(Level.INFO, "Not close enough: {0} vs {1}",
		            new Object[] {a,b});
		}
		assertTrue(Math.abs(a-b) < errorLimit);
	}
}